package bcontractor.builders;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Utility class to help coding immutable builder using less code.
 * 
 * @author lundberg
 * 
 */
public final class BuilderHelper {

    private static Method clone;

    private BuilderHelper() {

    }

    static {
        try {
            clone = Object.class.getDeclaredMethod("clone");
            clone.setAccessible(true);
        } catch (SecurityException e) {
            throw new RuntimeException("Unexpected exception obtaining clone.", e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Unexpected exception obtaining clone.", e);
        }
    }

    /**
     * Shallow copies the given original, but setting the given value at the
     * named attribute.
     * 
     * @param <T>
     *            type
     * @param original
     *            original
     * @param attribute
     *            attribute
     * @param value
     *            value
     * @return shallow copy with value as attribute value
     */
    public static <T> T copyWith(T original, String attribute, Object value) {
        T copy = copy(original);
        try {
            findField(original, attribute).set(copy, value);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("Unexpected error setting attribute.", e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unexpected error setting attribute.", e);
        }
        return copy;
    }

    /**
     * Shallow copies the given object
     * 
     * @param <T>
     *            type
     * @param original
     *            original
     * @return shallow copy
     */
    @SuppressWarnings("unchecked")
    public static <T> T copy(T original) {
        checkOriginal(original);
        try {
            return (T) clone.invoke(original);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("Unexpected error invoking clone.", e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unexpected error invoking clone.", e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Unexpected error invoking clone.", e);
        }
    }

    private static <T> void checkOriginal(T original) {
        if (original == null) {
            throw new NullPointerException("Attempt to copy null object.");
        }
    }

    private static Field findField(Object obj, String attribute) {
        Class<?> klass = obj.getClass();
        Field field = findFieldRecursion(klass, attribute);
        if (field == null) {
            throw new IllegalArgumentException(String.format("Attribute %s not found in class %s.", attribute, klass));
        }
        field.setAccessible(true);
        return field;
    }

    private static Field findFieldRecursion(Class<?> klass, String attribute) {
        if (klass == null) {
            return null;
        }
        try {
            return klass.getDeclaredField(attribute);
        } catch (NoSuchFieldException e) {
            return findFieldRecursion(klass.getSuperclass(), attribute);
        }
    }
}
